#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "vm.h"
#include "gc.h"
#include "builtins.h"

//#define DEBUG_COUNT_BUILTINS

#ifdef DEBUG_COUNT_BUILTINS
int count_builtin[50] = { 0 };
#endif

//#define DEBUG

void vm_put_place(unsigned char place_sort, scm place_index, scm obj) {
  switch(place_sort) {
  case 'r':
    vm_reg_ret = obj;
    break;
    
  case 'g':
    vm_set_global(place_index, obj);
    break;

  case 't':
    vm_set_stack_top(place_index, obj);
    break;
    
  case 'l':
    vm_set_stack_base(place_index, obj);
    break;
    
  case 'e':
    clos_set_env(vm_reg_env, place_index, obj);
    break;
    
  default:
    fprintf(stderr, "Unknown place sort %c\n", place_sort);
    exit(-1);
  }
}

void execute(void) {
  unsigned char c;

  while(1) {
#ifdef DEBUG
    char tmp_buf[512];
    strncpy(tmp_buf, (char*)(bytecode+place), 32);
    printf("<<<%s>>>\n", tmp_buf);
#endif
    
    c = bytecode[place++];
    switch(c) {
    case 'H':
      vm_halt();
      break;
      
    case 'Q':
      vm_quit();
      break;
      
    case 'B':
      vm_builtin();
      break;
      
    case 'A':
      vm_alloc();
      break;
      
    case 'c':
      vm_make_closure();
      break;
      
    case 'E':
      vm_set_closure();
      break;
      
    case 'C':
      vm_call();
      break;
      
    case 'T':
      vm_tail_call();
      break;
      
    case 'M':
      vm_move();
      break;
      
    case 'R':
      vm_return();
      break;
      
    case 'b':
      vm_branch();
      break;
      
    case 'J':
      vm_jump();
      break;
      
    default:
      fprintf(stderr, "Unknown VM instruction [%c] at %ld\n", c, place);
      //strncpy(tmp_buf, (char*)(bytecode), 32);
      //printf("<<<%s>>>\n", tmp_buf);
      //printf("%c %d\n", i1, blt_tsh);
      
      exit(-1);
    }
  }
}

void vm_halt() {
#ifdef DEBUG
  fprintf(stderr, "VM [%s]\n", "HALT");
#endif
  
  while(bytecode[place++] != '\n') {}
}

void vm_quit() {
#ifdef DEBUG
  fprintf(stderr, "VM [%s]\n", "QUIT");
#endif

#ifdef DEBUG_COUNT_BUILTINS
  for(int i = 0; i < 50; i++) {
	  printf("BLTN[%2d] used %4d times\n", i, count_builtin[i]);
  }
#endif

  exit(0);
}

void vm_builtin() {
#ifdef DEBUG
  fprintf(stderr, "VM [%s]\n", "BUILTIN");
#endif
  
  unsigned char place_sort;
  scm place_index;
  scm n;
  
  bytecode_read_place(&place_sort, &place_index);
  n = bytecode_read_n(2);
  bytecode_read_args();
  
#ifdef DEBUG
  fprintf(stderr, "VM [%s %ld]\n", "BUILTIN", n);
  for(int i = 0; i < bytecode_args_num; i++) {
    void builtin_display(scm);
    printf("ARG %d %ld\n", i, scm_get_tag(bytecode_args[i]));
    scm_display(bytecode_args[i]);
    puts("");
  }
  puts(".");
#endif
  
  builtin_handler h;
  scm res;
  
  h = handler[n];
  if(!h) {
    fprintf(stderr, "unimplemented or missing builtin %ld\n", n);
    exit(-1);
  }

  #ifdef DEBUG_COUNT_BUILTINS
  count_builtin[n]++;
  #endif

  res = h();
  vm_put_place(place_sort, place_index, res);
}

void vm_alloc() {
#ifdef DEBUG
  fprintf(stderr, "VM [%s]\n", "ALLOC");
#endif

  scm n;

  n = bytecode_read_n(2);
  
  stack_grow(n);
}

void vm_make_closure() {
#ifdef DEBUG
  fprintf(stderr, "VM [%s]\n", "MAKE-CLOSURE");
#endif

  unsigned char place_sort;
  scm place_index;
  scm lbl;
  scm n;

  bytecode_read_place(&place_sort, &place_index);
  lbl = bytecode_read_n(6);
  n = bytecode_read_n(2);

  scm clo;
  scm *ptr;
  int i;

  clo = heap_alloc_closure(n, lbl);
  ptr = get_clos(clo);
  for(i = 0; i < n; i++) {
    ptr[2 + i] = 0;
  }
  
  vm_put_place(place_sort, place_index, clo);
}

void vm_set_closure() {
#ifdef DEBUG
  fprintf(stderr, "VM [%s]\n", "SET-CLOSURE!");
#endif

  scm clo;
  scm n;
  scm val;

  clo = bytecode_read_val();
  n = bytecode_read_n(2);
  val = bytecode_read_val();
  
  clos_set_env(clo, n, val);
}

void vm_call() {
#ifdef DEBUG
  fprintf(stderr, "VM [%s]\n", "CALL");
#endif

  scm clo;
  int i;

  clo = bytecode_read_val();
  bytecode_read_args();

  stack_push(0xC0DEDBADC0DEDBAD);
  stack_push(vm_stack_base_ptr);
  stack_push(vm_reg_env);
  stack_push(place);
  stack_push(0xC0FFEEEEEEEEEEEE);

  vm_stack_base_ptr = vm_stack_ptr;
  for(i = 0; i < bytecode_args_num; i++) {
    stack_push(bytecode_args[i]);
  }
  
  vm_reg_env = clo;
  place = clos_lbl(clo);
}

void vm_tail_call() {
#ifdef DEBUG
  fprintf(stderr, "VM [%s]\n", "TAIL-CALL");
#endif
  
  scm clo;
  int i;
  
  clo = bytecode_read_val();
  bytecode_read_args();
  
  vm_stack_ptr = vm_stack_base_ptr;
  for(i = 0; i < bytecode_args_num; i++) {
    stack_push(bytecode_args[i]);
  }
  
  vm_reg_env = clo;
  place = clos_lbl(clo);
}

void vm_move() {
#ifdef DEBUG
  fprintf(stderr, "VM [%s]\n", "MOVE");
#endif
  
  unsigned char place_sort;
  scm place_index;
  scm val;

  bytecode_read_place(&place_sort, &place_index);
  val = bytecode_read_val();
  
  vm_put_place(place_sort, place_index, val);
}

void vm_return() {
#ifdef DEBUG
  fprintf(stderr, "VM [%s]\n", "RETURN");
#endif

  scm tmp;
  
  vm_stack_ptr = vm_stack_base_ptr;
  
  tmp = stack_pop();
  assert(tmp == 0xC0FFEEEEEEEEEEEE);
  place = stack_pop();
  vm_reg_env = stack_pop();
  vm_stack_base_ptr = stack_pop();
  tmp = stack_pop();
  assert(tmp == 0xC0DEDBADC0DEDBAD);
}

void vm_branch() {
#ifdef DEBUG
  fprintf(stderr, "VM [%s]\n", "BRANCH");
#endif

  scm ip;

  ip = bytecode_read_n(6);

  if(vm_reg_ret != atom_tag_fals)
    place = ip;
}

void vm_jump() {
#ifdef DEBUG
  fprintf(stderr, "VM [%s]\n", "JUMP");
#endif

  scm ip;

  ip = bytecode_read_n(6);

  place = ip;
}
